﻿// Copyright 2007-2009 Stuart Caborn, Alex Scordellis
//  
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
//  use this file except in compliance with the License. You may obtain a copy of
//  the License at
//  
//  http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
//  License for the specific language governing permissions and limitations under
//  the License.
// 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace NSynthesis.Reflection
{
    public static class MethodMatcher
    {
        /// <summary>
        /// Determines if a method could possibly provide an implementation
        /// of the method provided. Does not care if the two methods are in
        /// unrelated classes / class hierarchies.
        /// 
        /// In NSynthesis, we use the <see cref="TypeMatcher"/> to determine
        /// which classes could provide implementations of a mock call, then 
        /// use this method to determine which methods on those classes
        /// are implementations which should be unit tested.
        /// </summary>
        /// <param name="candidate">The method to inspect.</param>
        /// <param name="mockCall">The info that PostSharp gave us when we intercepted
        /// the actual mock call.</param>
        /// <returns>True if candidate could be a production implementation of mockCall.</returns>
        public static bool CouldImplement(this MethodInfo candidate, MethodInfo mockCall)
        {
            if (candidate.IsAbstract)
                return false;

            return candidate.Name.Equals(mockCall.Name) && candidate.HasSameSignatureAs(mockCall);
        }

        private static bool HasSameSignatureAs(this MethodBase candidate, MethodBase mockCall)
        {
            if (candidate.GetParameters().Length != mockCall.GetParameters().Length)
                return false;

//            if (candidate.IsGenericMethod)
//                return mockCall.GetGenericMethodDefinition().GetBaseDefinition().Equals(candidate);

            return AllParametersMatch(candidate, mockCall);
        }

        private static bool AllParametersMatch(MethodBase candidate, MethodBase mockCall)
        {
            return candidate.GetParameters().Select(info => info.ParameterType)
                .Zip(mockCall.GetParameters().Select(info => info.ParameterType), (candidateParameterType, mockCallParameterType) => 
                     {
                         Type effectiveTypeToCompareWithMockCall = candidateParameterType;
                         if (candidateParameterType.IsGenericParameter)
                         {
                             List<Type> genericArgumentListOfCandidate;
                             Type[] genericArgumentListOfMock;

                             if (candidateParameterType.DeclaringMethod == null)
                             {
                                 // We're dealing with a generic argument from the type definition
                                 genericArgumentListOfCandidate = new List<Type>(candidate.DeclaringType.GetGenericArguments());
                                 genericArgumentListOfMock = mockCall.DeclaringType.RemoveProxy().GetGenericArguments();
                             }
                             else
                             {
                                 // We're dealing with a generic argument from the method definition
                                 genericArgumentListOfCandidate = new List<Type>(candidate.GetGenericArguments());
                                 genericArgumentListOfMock = mockCall.GetGenericArguments();
                             }

                             effectiveTypeToCompareWithMockCall = genericArgumentListOfMock[genericArgumentListOfCandidate.IndexOf(candidateParameterType)];
                         }
                         return mockCallParameterType.Equals(effectiveTypeToCompareWithMockCall);
                     }).All();
        }
    }
}